package com.gitee.wsl.model.process.plugins

import com.gitee.wsl.annotation.InternalAPI
import com.gitee.wsl.flowing.pipeline.PipelinePhase
import com.gitee.wsl.model.ModelHook
import com.gitee.wsl.model.OutgoingContent
import com.gitee.wsl.model.process.ProcessModel
import com.gitee.wsl.model.process.ProcessModelHook
import com.gitee.wsl.model.process.ProcessModelPlugin
import com.gitee.wsl.model.process.content.ObservableContent
import com.gitee.wsl.model.process.createProcessPlugin
import com.gitee.wsl.model.process.request.ProcessRequestBuilder
import com.gitee.wsl.model.process.request.RequestPipeline
import com.gitee.wsl.model.process.response.ProcessResponse
import com.gitee.wsl.model.process.response.ReceivePipeline
import com.gitee.wsl.prop.AttributeKey

/**
 * Callback that can be registered to listen for upload/download progress.
 * @param bytesSentTotal number of transmitted bytes.
 * @param contentLength body size. Can be null if the size is unknown.
 */
public fun interface ProgressListener {
    /**
     * Invokes every time some data is flushed through the [ByteReadChannel].
     */
    public suspend fun onProgress(bytesSentTotal: Long, contentLength: Long?)
}


private val UploadProgressListenerAttributeKey =
    AttributeKey<ProgressListener>("UploadProgressListenerAttributeKey")

private val DownloadProgressListenerAttributeKey =
    AttributeKey<ProgressListener>("DownloadProgressListenerAttributeKey")

/**
 * Plugin that provides observable progress for uploads and downloads
 */
public val BodyProgress = createProcessPlugin("BodyProgress") {

    on(AfterRenderHook) { request, content ->
        val listener = request.attributes.getOrNull(UploadProgressListenerAttributeKey) ?: return@on null

        ObservableContent(content, request.executionContext, listener)
    }

    on(AfterReceiveHook) { response ->
        val listener = response.call.request.attributes.getOrNull(DownloadProgressListenerAttributeKey) ?: return@on null
        response.withObservableDownload(listener)
    }
}

internal object AfterReceiveHook : ProcessModelHook<suspend (ProcessResponse) -> ProcessResponse?> {
    override fun install(client: ProcessModel, handler: suspend (ProcessResponse) -> ProcessResponse?) {
        client.receivePipeline.intercept(ReceivePipeline.After) { response ->
            val newResponse = handler(response)
            if (newResponse != null) proceedWith(newResponse)
        }
    }
}

internal object AfterRenderHook : ProcessModelHook<suspend (ProcessRequestBuilder, OutgoingContent) -> OutgoingContent?> {
    override fun install(
        client: ProcessModel,
        handler: suspend (ProcessRequestBuilder, OutgoingContent) -> OutgoingContent?
    ) {
        val observableContentPhase = PipelinePhase("ObservableContent")
        client.requestPipeline.insertPhaseAfter(reference = RequestPipeline.Render, phase = observableContentPhase)
        client.requestPipeline.intercept(observableContentPhase) { content ->
            if (content !is OutgoingContent) return@intercept
            val newContent = handler(context, content) ?: return@intercept
            proceedWith(newContent)
        }
    }
}

@OptIn(InternalAPI::class)
internal fun ProcessResponse.withObservableDownload(listener: ProgressListener): ProcessResponse {
//    val observableByteChannel = rawContent.observable(coroutineContext, contentLength(), listener)
//    return call.wrapWithContent(observableByteChannel).response
    return this
}

/**
 * Registers listener to observe download progress.
 */
public fun ProcessRequestBuilder.onDownload(listener: ProgressListener?) {
    if (listener == null) {
        attributes.remove(DownloadProgressListenerAttributeKey)
    } else {
        attributes.put(DownloadProgressListenerAttributeKey, listener)
    }
}

/**
 * Registers listener to observe upload progress.
 */
public fun ProcessRequestBuilder.onUpload(listener: ProgressListener?) {
    if (listener == null) {
        attributes.remove(UploadProgressListenerAttributeKey)
    } else {
        attributes.put(UploadProgressListenerAttributeKey, listener)
    }
}
